Skip to content

Falcon: native implementation replacing liboqs, with crypto callbacks and ARM acceleration. Deprecate liboqs support.#10827

Open
danielinux wants to merge 38 commits into
wolfSSL:masterfrom
danielinux:falcon-native
Open

Falcon: native implementation replacing liboqs, with crypto callbacks and ARM acceleration. Deprecate liboqs support.#10827
danielinux wants to merge 38 commits into
wolfSSL:masterfrom
danielinux:falcon-native

Conversation

@danielinux

Copy link
Copy Markdown
Member

Description

Replaces wolfSSL's liboqs-based Falcon wrapper with a complete native
wolfCrypt implementation
of Falcon (key generation, signing and
verification, levels 1 and 5), removes the liboqs dependency entirely, adds the
standard crypto-callback interface, and adds optional per-architecture
acceleration for x86-64 and ARM.

What's in it

Native implementation (falcon.c + wc_falcon*.c, no liboqs):

  • Public API wc_falcon_* / falcon_key (single surface; the redundant
    wc_FnDsa_* layer is gone). Verify uses an integer NTT mod q=12289 with
    cached twiddle tables; signing uses the FFT / ffLDL tree + discrete Gaussian
    sampler; keygen uses the RNS/big-integer NTRU solver.
  • Floating-point fpr seam with selectable backends:
    • portable, constant-time integer-emulated FP — the default;
    • --enable-falcon-double — inline native double (fastest with a
      constant-time HW FPU);
    • --enable-falcon-asm — x86-64 SSE2 out-of-line fpr backend
      (generated; see companion wolfssl-scripts PR);
    • --enable-falcon-avx2 — x86-64 AVX2 (4-wide) FFT.
  • Division-free (Barrett) verify NTT, so no hardware divide is required on
    Cortex-M / embedded targets.

Remove liboqs: Falcon was the last liboqs consumer. Drops --with-liboqs
(and -loqs), the wolfcrypt/src/port/liboqs layer, the CMake WOLFSSL_OQS
option / FindOQS.cmake, and the VS/Zephyr/INTIME/Docker references. The
library links no liboqs. HAVE_FALCON is added to the experimental-settings
gate so the requirement holds across every build system.

Crypto callbacks (WOLF_CRYPTO_CB): keygen/sign/verify dispatch to
wc_CryptoCb_MakePqcSignatureKey / PqcSign / PqcVerify
(WC_PQC_SIG_TYPE_FALCON), with software fall-through. Adds
WOLF_CRYPTO_CB_ONLY_FALCON (first PQC signature to have it, mirroring
_RSA/_ECC): the callback becomes authoritative and the native core is
compiled out.

ARM acceleration (portable-C + intrinsics, no hand-asm):

  • Cortex-M DSP verify NTT — auto-enabled on __ARM_FEATURE_DSP
    (Cortex-M4/M7/M33). Packed 2-lane butterflies (SMLABB/SMLATB +
    SADD16/SSUB16 + USUB16/SEL) and an SMUAD squared-norm; bit-identical
    to the scalar path.
  • AArch64 NEON FFT--enable-falcon-neon, the 2-wide float64x2_t + FMA
    counterpart of the AVX2 FFT for the signing/keygen path.

Testing

  • wolfcrypt/test falcon_test: KAT verify + native keygen/sign/verify
    round-trip; the crypto-callback test drives every Falcon op through the
    callback and asserts the cb path was taken (incl. a CB_ONLY check).
  • Native ↔ liboqs interop harness + CI workflow (both directions,
    keygen+sign vs verify).
  • Invariant fuzzer: ~79k random seeds per level, checking the exact NTRU
    equation f·G − g·F = q on every keygen, h·f ≡ g (mod q), the norm bound,
    sign→native+liboqs verify, and forgery rejection — zero failures observed with this set.
  • Emulator tests for ARM acceleration (both under IDE/): m33mu-falcon-verify runs the DSP
    NTT on an STM32H563/Cortex-M33 (m33mu) — genuine sig accepted, tampered
    rejected; qemu-falcon-neon runs a full keygen→sign→verify at levels 1 and 5
    on qemu-system-aarch64 with the NEON FFT.
  • Builds green (testwolfcrypt + full testsuite) across default / cryptocb /
    CB_ONLY / verify-only and each fpr backend.

Build options

--enable-falcon native Falcon (requires --enable-experimental)
--enable-falcon-double inline native-double fpr backend
--enable-falcon-asm x86-64 SSE2 fpr asm backend
--enable-falcon-avx2 x86-64 AVX2 FFT
--enable-falcon-neon AArch64 NEON FFT

Checklist

  • added tests
  • updated/added doxygen
  • updated appropriate READMEs
  • Updated manual and documentation

Copilot AI review requested due to automatic review settings July 1, 2026 08:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the prior liboqs-backed Falcon integration with a native wolfCrypt Falcon implementation (keygen/sign/verify for levels 1 & 5), adds crypto-callback dispatch for Falcon operations, and removes the liboqs dependency across build systems and integrations. It also introduces optional architecture-specific acceleration paths (x86-64 and ARM) and adds CI/harnesses to validate native↔liboqs interoperability.

Changes:

  • Replaces Falcon’s liboqs wrapper with native wolfCrypt Falcon core + new internal modules (FFT/fpr seam/sampler/poly/codec/bigint/keygen/sign).
  • Removes liboqs build/config/install references across autotools/CMake/IDE/Zephyr/VS/Docker, and updates docs accordingly.
  • Adds Falcon crypto-callback integration and optional x86-64/ARM acceleration plus new CI workflow + interop harness and emulator projects.

Reviewed changes

Copilot reviewed 67 out of 68 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
zephyr/CMakeLists.txt Removes liboqs port source from Zephyr build.
wrapper/CSharp/wolfssl.vcxproj Removes liboqs port source from C# wrapper project.
wolfssl/wolfcrypt/wc_falcon_sign.h New internal signing orchestration API for native Falcon.
wolfssl/wolfcrypt/wc_falcon_sampler.h New internal sampler API for signing-side Gaussian sampling.
wolfssl/wolfcrypt/wc_falcon_poly.h New internal FFT-domain polynomial ops API.
wolfssl/wolfcrypt/wc_falcon_keygen.h New internal key generation API for native Falcon.
wolfssl/wolfcrypt/wc_falcon_fpr.h New fpr seam API + backend selection surface.
wolfssl/wolfcrypt/wc_falcon_fft.h New internal FFT API + twiddle table declaration.
wolfssl/wolfcrypt/wc_falcon_codec.h New internal encode/decode routines for sign/keygen paths.
wolfssl/wolfcrypt/wc_falcon_bigint.h New internal bigint/RNS arithmetic API for keygen.
wolfssl/wolfcrypt/settings.h Drops liboqs/Falcon coupling; makes HAVE_FALCON the gate.
wolfssl/wolfcrypt/port/liboqs/liboqs.h Removes liboqs port header.
wolfssl/wolfcrypt/include.am Removes liboqs header install listing.
wolfssl/wolfcrypt/falcon.h Updates public Falcon API/macros to native implementation and adds CB_ONLY hooks.
wolfssl.vcxproj Removes liboqs port source from VS project.
wolfssl.vcproj Removes liboqs port source from legacy VS project.
wolfssl-VS2022.vcxproj Removes liboqs port source from VS2022 project.
wolfcrypt/src/wc_port.c Removes liboqs init/cleanup hooks from wolfCrypt init lifecycle.
wolfcrypt/src/wc_falcon_sampler.c New SHAKE256-based PRNG + Gaussian sampler implementation.
wolfcrypt/src/wc_falcon_poly.c New FFT-domain polynomial primitives implementation.
wolfcrypt/src/wc_falcon_fpr_x86_64_asm.S New x86-64 asm fpr backend (generated).
wolfcrypt/src/wc_falcon_fft_neon.c New AArch64 NEON FFT backend for signing/keygen.
wolfcrypt/src/wc_falcon_codec.c New native Falcon codec implementation for sign/keygen paths.
wolfcrypt/src/port/liboqs/liboqs.c Removes liboqs port implementation source.
wolfcrypt/src/include.am Removes liboqs port source from dist list.
wolfcrypt/src/falcon.c Reworks public wc_falcon_* wrapper to dispatch to native core and crypto callbacks.
src/tls.c Removes liboqs mention; updates ECC extension error text to ML-KEM wording.
src/include.am Adds native Falcon sources and optional asm/AVX2/NEON sources under new conditionals.
scripts/falcon-interop.c New native↔liboqs Falcon interop harness program.
INSTALL Updates Falcon enablement instructions (native Falcon + experimental gate).
IDE/qemu-falcon-neon/user_settings.h New bare-metal AArch64 NEON test settings.
IDE/qemu-falcon-neon/start.S New AArch64 minimal reset/FP+MMU bring-up for QEMU test.
IDE/qemu-falcon-neon/README.md New documentation for AArch64 NEON FFT QEMU test.
IDE/qemu-falcon-neon/Makefile New build recipe for the QEMU AArch64 NEON test firmware.
IDE/qemu-falcon-neon/main.c New AArch64 firmware exercising native Falcon sign/verify with NEON FFT.
IDE/qemu-falcon-neon/link.ld New linker script for QEMU AArch64 test firmware.
IDE/qemu-falcon-neon/.gitignore New ignore rules for generated QEMU test artifacts.
IDE/m33mu-falcon-verify/user_settings.h New Cortex-M33 verify-only test settings.
IDE/m33mu-falcon-verify/target.ld New linker script for STM32H563 (m33mu) verify test.
IDE/m33mu-falcon-verify/syscalls.c New minimal syscall stubs for bare-metal test.
IDE/m33mu-falcon-verify/README.md New documentation for Cortex-M DSP verify acceleration test.
IDE/m33mu-falcon-verify/Makefile New build recipe for Cortex-M33 verify-only firmware.
IDE/m33mu-falcon-verify/main.c New firmware exercising Falcon-512 verify and tamper rejection.
IDE/m33mu-falcon-verify/kat.h New embedded Falcon-512 KAT vectors for the verify test.
IDE/m33mu-falcon-verify/ivt.c New interrupt vector table for bare-metal Cortex-M test.
IDE/m33mu-falcon-verify/.gitignore New ignore rules for generated m33mu test artifacts.
IDE/INTIME-RTOS/Makefile Removes liboqs header directory handling in INTIME packaging.
IDE/INTIME-RTOS/libwolfssl.vcxproj Removes liboqs port source from INTIME VS project.
Docker/Dockerfile Removes liboqs install step from Docker build environment.
doc/dox_comments/header_files/falcon.h New doxygen documentation for Falcon public API.
doc/dox_comments/header_files/doxygen_groups.h Adds Falcon doxygen group definition.
configure.ac Removes liboqs option; adds Falcon native options + feature conditionals and flags.
CMakeLists.txt Removes liboqs options and adds native Falcon option + required SHA-3/SHAKE256 defs.
cmake/options.h.in Removes HAVE_LIBOQS config define emission.
cmake/modules/FindOQS.cmake Removes liboqs CMake find-module.
cmake/include.am Removes FindOQS.cmake from distributed files.
cmake/functions.cmake Removes liboqs helper source inclusion and adds Falcon source list plumbing.
.github/workflows/falcon-interop.yml New CI workflow building liboqs baseline and running native↔liboqs interop matrix.
.github/workflows/cmake.yml Removes explicit WOLFSSL_OQS CMake option usage in CI workflow.
Comments suppressed due to low confidence (1)

wolfcrypt/src/falcon.c:130

  • wc_falcon_sign_msg() can call the crypto-callback and/or native signer with rng==NULL, which can lead to a NULL dereference or use of an uninitialized RNG. Also, in verify-only builds the function should not attempt a signing crypto-callback before returning NOT_COMPILED_IN (per the verify-only contract described in falcon.h).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmake/functions.cmake Outdated
Comment thread wolfcrypt/src/wc_falcon_sampler.c Outdated
Comment thread wolfcrypt/src/wc_falcon_sampler.c Outdated
Comment thread wolfcrypt/src/wc_falcon_fft_neon.c
Comment thread wolfcrypt/src/wc_falcon_fft_neon.c
Comment thread wolfssl/wolfcrypt/include.am
@danielinux danielinux changed the title Falcon: native implementation replacing liboqs, with crypto callbacks and ARM acceleration. Deprecate libOQS support. Falcon: native implementation replacing liboqs, with crypto callbacks and ARM acceleration. Deprecate liboqs support. Jul 1, 2026
@julek-wolfssl
julek-wolfssl requested a review from Copilot July 1, 2026 10:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 73 changed files in this pull request and generated 7 comments.

Comment thread wolfssl/wolfcrypt/wc_falcon_codec.h Outdated
Comment thread wolfcrypt/src/falcon.c
Comment thread wolfcrypt/src/falcon.c
Comment thread wolfcrypt/src/wc_falcon_sign.c Outdated
Comment thread wolfcrypt/src/wc_falcon_sign.c Outdated
Comment thread wolfcrypt/src/wc_falcon.c Outdated
Comment thread INSTALL

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #10827

Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src

Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Findings are non-blocking.

Comment thread wolfcrypt/src/falcon.c
Comment thread wolfcrypt/src/wc_falcon.c Outdated

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some smaller initial things regarding the infrastructure. I haven't reviewed the heavy math stuff, this is probably better handled by @SparkiDev.

Comment thread zephyr/CMakeLists.txt
Comment thread configure.ac Outdated
Comment thread configure.ac Outdated
Comment thread wolfcrypt/src/falcon.c Outdated
@danielinux danielinux self-assigned this Jul 6, 2026
@danielinux
danielinux requested a review from Frauschi July 6, 2026 08:09
@danielinux danielinux assigned Frauschi and unassigned danielinux Jul 8, 2026

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐺 Skoll Code Review

Overall recommendation: REQUEST_CHANGES
Findings: 8 total — 8 posted, 0 skipped

Posted findings

  • [Medium] Private-key NTT(f) left in freed heap in falcon_compute_public (no ForceZero)wolfcrypt/src/wc_falcon_keygen.c:310
  • [Medium] Sampler rejection loop can hang on a mid-signature PRNG failurewolfcrypt/src/wc_falcon_sampler.c:321
  • [Medium] Recursion used in signing path violates wolfSSL no-recursion conventionwolfcrypt/src/wc_falcon_sign.c:367
  • [Low] SHAKE256 context leaked when sampler init fails after wc_InitShake256 succeedswolfcrypt/src/wc_falcon_sampler.c:133
  • [Low] wc_falcon_check_key doxygen overstates behavior (no cryptographic cross-check)wolfcrypt/src/falcon.c:657
  • [Low] Undefined shift in trim_i8 encode/decode if bits is 0 or >= 32wolfcrypt/src/wc_falcon_codec.c:189
  • [Info] Internally allocated h scratch freed without ForceZerowolfcrypt/src/wc_falcon_keygen.c:1892
  • [Info] PRNG re-squeezes on every call once error is latchedwolfcrypt/src/wc_falcon_sampler.c:158

Review generated by Skoll via Claude/Codex

Comment thread wolfcrypt/src/wc_falcon_keygen.c Outdated
Comment thread wolfcrypt/src/wc_falcon_sampler.c Outdated
Comment thread wolfcrypt/src/wc_falcon_sign.c Outdated
Comment thread wolfcrypt/src/wc_falcon_sampler.c Outdated
Comment thread wolfcrypt/src/falcon.c
Comment thread wolfcrypt/src/wc_falcon_codec.c Outdated
Comment thread wolfcrypt/src/wc_falcon_keygen.c Outdated
Comment thread wolfcrypt/src/wc_falcon_sampler.c Outdated
@danielinux
danielinux requested a review from Frauschi July 9, 2026 13:25
@danielinux

Copy link
Copy Markdown
Member Author

405db5c ran in fuzzer with 60,000,144 sign/verify round-trips, zero failures.

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐺 Skoll Code Review

Overall recommendation: APPROVE
Findings: 5 total — 4 posted, 1 skipped

Posted findings

  • [Low] Stale "Phase 1: verification only" file header commentwolfcrypt/src/wc_falcon.c:22-26
  • [Low] wc_falcon_verify_msg doc comment references non-existent contextLenwolfcrypt/src/falcon.c:160-162
  • [Medium] Sign compression-fit retry silently caps at 32 attemptswolfcrypt/src/wc_falcon.c:824-855
  • [Medium] No unit test exercises the concat(priv,pub) legacy import size path directlywolfcrypt/src/falcon.c:448-484
Skipped findings
  • [Low] Bare-scope { } blocks used to introduce mid-function declarations

Review generated by Skoll via Claude/Codex

Comment thread wolfcrypt/src/wc_falcon.c Outdated
Comment thread wolfcrypt/src/falcon.c
Comment thread wolfcrypt/src/wc_falcon.c Outdated
Comment thread wolfcrypt/src/falcon.c
@Frauschi

Copy link
Copy Markdown
Contributor

🟡 [Medium] Alternate fpr/FFT backends (asm, double, avx2, neon) not exercised by CI
💡 SUGGEST test

The added CI job builds only ./configure --enable-falcon --enable-experimental (.github/workflows/falcon-interop.yml:126) — i.e. just the default portable-integer fpr backend on x86-64. The four selectable backends (asm, double, avx2, neon) are the highest-risk new code (SIMD intrinsics, generated asm), yet they're currently validated only out-of-band via the IDE/ emulator harnesses.

Good news: all four can run on GitHub-hosted runners — none need self-hosted hardware or a mandatory emulator. Backend dispatch is compile-time with no runtime CPUID fallback, so the runner CPU is the deciding factor. What each needs to execute:

Backend ISA needed to run Runner
double none (hardware double) any (ubuntu-latest)
asm SSE2 scalar (movsd/addsd/mulsd) — baseline on all x86-64 ubuntu-latest
avx2 AVX2 + FMA3 — present on all GitHub x86 runners (Xeon 8272CL/8370C, EPYC 7763) ubuntu-latest
neon ARM64 NEON + FMA ubuntu-24.04-arm (free ARM64 runners for public repos) or macos-14 (Apple Silicon)

The avx2 TU uses per-function __attribute__((target("avx2,fma"))), so it compiles cleanly without a global -mavx2 — only the runtime CPU needs the features, and GitHub's standard runners have had AVX2+FMA3 for years.

Suggested matrix (respecting the configure constraints — double XOR asm, and avx2/neon imply double):

  • ubuntu-latest (x86-64): matrix --enable-falcon={asm,double,avx2} → build + run testwolfcrypt. Covers 3 of 4 natively.
  • ubuntu-24.04-arm (ARM64, free for public repos): --enable-falcon=neon → build + run. Covers NEON natively.

One caveat: since backend selection is compile-time with no CPUID guard, an avx2 CI job is implicitly pinned to AVX2-capable runners. That's effectively always true on GitHub, but a one-line grep -q avx2 /proc/cpuinfo guard (or runtime dispatch) would make it robust if a runner class ever changes.

The falcon_interop job restored/untarred liboqs into the workspace and only
then ran actions/checkout, whose "git clean -ffdx" deleted the untracked
oqs-install/ directory. The interop harness build then failed with
"oqs/oqs.h: No such file or directory".

Move the wolfSSL checkout ahead of the cache-restore / artifact-download /
untar steps so the liboqs install lands after the clean and survives.

The harness itself is fine: built locally against liboqs it passes all eight
interop cells (liboqs<->native, both directions, levels 1 and 5).
Falcon had crypto-level coverage (KAT + native round-trip in
wolfcrypt/test/test.c) but, unlike ML-DSA and SLH-DSA, no dedicated
tests/api/ unit test exercising the public wc_falcon_* / wc_Falcon_* API
surface. This adds one, wired into the unit test runner as the "falcon"
group.

Coverage (both Falcon-512 / L1 and Falcon-1024 / L5, which are always
compiled together):
- sizes:        size/priv_size/pub_size/sig_size vs the spec constants,
                get_level round-trip, and NULL / unset-level rejection.
- make_key:     NULL and unset-level rejection; real keygen -> check_key.
- sign_vfy:     sign -> verify; wrong-message and one-byte tamper rejected;
                too-small buffer -> BUFFER_E with the required length set;
                verify with no public key -> BAD_FUNC_ARG.
- import_export: public / private-only (raw) / private (concat) / export_key
                round-trips, each re-signed or verified, plus too-small
                (BUFFER_E) and wrong-size (BAD_FUNC_ARG) paths.
- check_key:    valid pass; corrupted public copy, public-only and
                private-only keys all fail (PUBLIC_KEY_E); NULL rejected.
- der:          KeyToDer / PrivateKeyToDer / PublicKeyToDer round-trips via
                PrivateKeyDecode / PublicKeyDecode, size-query (NULL output),
                and the SetAsymKeyDer too-small contract (BAD_FUNC_ARG).
- error_paths:  exhaustive NULL / bad-level / wrong-size / no-key-set
                argument sanitising for every public entry point.

Tests requiring key generation or signing are gated on
WC_FALCON_HAVE_NATIVE_SIGN so the file also builds in
WOLFSSL_FALCON_VERIFY_ONLY and WOLF_CRYPTO_CB_ONLY_FALCON configurations;
size and argument-sanitising tests run in every HAVE_FALCON build.

Verified: 7/7 pass under both --enable-falcon-avx2 and the default
constant-time build; compiles clean with WOLFSSL_FALCON_VERIFY_ONLY.
The new tests/api/test_falcon.c was wired into the automake unit test
(tests/api/include.am) but not the CMake one, so CMake-based CI builds
failed to link the unit test with undefined references to
test_wc_falcon_*. Add it to the CMake unit_test source list alongside the
other tests/api/test_*.c entries.
- wc_falcon.c: replace the lazily-initialised, mutable NTT twiddle-table
  cache with precomputed read-only const tables (falcon_zetas/izetas_l1/l5).
  This removes the data race on the shared cache (a reader could see the
  init flag set before all table entries were visible) and also drops the
  now-unused falcon_brv / falcon_build_tables helpers. Verify stays fast
  (const reads, no per-call rebuild); KAT confirms the values.
- falcon.c: correct the wc_falcon_sign_msg doc comment (signing needs the
  *private* key; required buffer size is the active level's signature size,
  not always FALCON_LEVEL1_SIG_SIZE) and validate rng != NULL in the
  software path so the error is reported at the API boundary.
- wc_falcon_sign.c / wc_falcon_sign.h: route key->heap into
  falcon_complete_private and falcon_expand_privkey (their large fpr scratch
  allocations previously used a NULL heap hint, bypassing custom heap
  routing); and fail fast in falcon_do_sign_tree on the sampler's sticky
  PRNG error (passed in as samplerErr) instead of running to the restart
  bound.
- wc_falcon_codec.h: use the project-standard WOLF_CRYPT_WC_FALCON_CODEC_H
  include guard instead of the collision-prone generic FALCON_CODEC_H.
- doc/dox_comments (ssl.h + -ja): drop the stale "or HAVE_LIBOQS" from the
  ML-KEM hybrid group requirements; liboqs is no longer supported.
- falcon.c (wc_falcon_import_private_only): call falcon_store_pub_behind_priv
  unconditionally after setting prvKeySet. The raw-size branch previously only
  synced the behind-private public copy in the concat layout, so importing a
  public key first and then a raw private key left key->k + KEY_SIZE zero and
  made wc_falcon_check_key return a false PUBLIC_KEY_E. Added a regression case
  to test_wc_falcon_check_key covering the public-then-raw-private ordering.
- wc_falcon.c (native sign cleanup): free the sampler's SHAKE256 context with
  wc_Shake256_Free(&spc.p.shake) when it was initialized, before ForceZero.
  Without it, WOLFSSL_ASYNC_CRYPT + WC_ASYNC_ENABLE_SHA3 builds leaked the
  async device context allocated by wc_InitShake256 on every sign, unlike the
  keygen and hash-to-point paths which already free their SHAKE contexts.
configure.ac sets enable_option_checking=fatal, so removing the
--with-liboqs option turned it into a hard "unrecognized options" error.
Test matrices / CI (e.g. the Jenkins PRB liboqs config branch) and user
scripts that still pass --with-liboqs then fail at the configuration phase
before any build runs.

Re-add --with-liboqs as a recognised, deprecated no-op that warns it has no
effect and points at --enable-falcon. It links no liboqs and does not change
the build (SBOM unaffected); it only prevents the fatal unknown-option abort.
Mirrors the existing --enable-cryptodev deprecation shim.
The check-source-text unknown-macro check (subtest K) flags preprocessor
symbols used in the sources that are neither #defined in a header nor listed
in .wolfssl_known_macro_extras. The native Falcon backends add several
configure/-D build gates that are not #defined anywhere, so a build that
scans the Falcon sources without those options set (e.g. --enable-all
without the sub-backends) reported them as unknown.

Register the nine new gates (LC_ALL=C sorted):
  WOLF_CRYPTO_CB_ONLY_FALCON
  WOLFSSL_FALCON_FFT_AVX2, WOLFSSL_FALCON_FFT_NEON
  WOLFSSL_FALCON_FPR_ASM, WOLFSSL_FALCON_FPR_DOUBLE
  WOLFSSL_FALCON_NTT_DSP, WOLFSSL_FALCON_NO_NTT_DSP
  WOLFSSL_FALCON_SIGN_STATS, WOLFSSL_FALCON_VERIFY_ONLY

HAVE_FALCON and WC_FALCON_HAVE_NATIVE_SIGN are already known (configure /
falcon.h), so they are not added.
The sign retry loop capped the compressed signature at the caller-supplied
*outLen instead of the level's fixed size, so a buffer larger than the max
let an over-length (e.g. 667-byte Falcon-512) signature escape without a
restart, and verification then rejected it. Cap at sigMax instead.
tests/falcon/ hammers make_key -> sign -> verify across many keys and
messages to catch intermittent non-verifying signatures, dumping a
replayable artifact on any mismatch. Includes Makefile and run.sh.
The keygen/sign/verify fuzzer now lives in the testing repo
(fuzzers/falcon); it is not part of the library or its CI. Removing it
here also drops tests/falcon/run.sh from the PR's changed-shell set.
On 32-bit ARM targets lacking a double-precision FPU (Cortex-M0/M3/M4/
M23/M33), C double maps onto slow libgcc soft-double, making the opt-in
WOLFSSL_FALCON_FPR_DOUBLE backend a pessimization versus the default
integer fpr backend. Emit a #warning to steer users away. Detect via
__ARM_FP bit 3; AArch64 does not define __arm__ so it is unaffected.
…s, footprint)

Four fixes from PR review:

- zephyr/CMakeLists.txt: the native port split falcon.c into wc_falcon_*.c
  translation units; add the portable sources so a Zephyr build with Falcon
  links. x86-64 asm/AVX2 and the NEON backend are left out (not selected by any
  Zephyr config).

- configure.ac: fold the standalone --enable-falcon-{asm,double,avx2,neon}
  switches into comma-separated sub-options of --enable-falcon
  (e.g. --enable-falcon=avx2), matching the common wolfSSL idiom. avx2/neon
  imply the double backend after arch-gating so ignoring an unsupported vector
  backend does not clobber an explicit 'double'. Sweep the qemu-falcon-neon doc
  to the new spelling.

- configure.ac: align the Falcon line in the two feature summaries.

- falcon.c/falcon.h: drop the duplicate public-key copy kept behind the private
  key. Its only remaining reader was wc_falcon_check_key, whose compare was
  against a copy of the same bytes and so could never detect a real mismatch;
  wc_falcon_export_private already rebuilds the concat layout on demand. Shrink
  key->k from FALCON_MAX_PRV_KEY_SIZE to FALCON_MAX_KEY_SIZE (saves 1793 bytes
  per key at level 5). check_key now verifies both halves are present and
  documents a full cryptographic cross-check as a follow-up. Update the unit
  test that relied on the old in-memory-copy compare.
The DSP-NTT gate in wc_falcon.c keys off the compiler-predefined
__ARM_FEATURE_DSP, and the FPR_DOUBLE soft-double guard in wc_falcon_fpr.h
keys off __ARM_FP; neither was listed in .wolfssl_known_macro_extras, so
the source-text unknown-macro check flags them. Add both in sorted order.
wolfSSL C style forbids recursion. The reference implementation expresses
the ffLDL tree construction, tree normalization and Fast Fourier sampling
as self-recursions on logn; flatten all three into iterations over small
fixed-size frame stacks (depth bounded by logn <= 10):

- ffLDL_fft_inner and ffLDL_binary_normalize do all their work before
  their two half-degree sub-invocations, so a LIFO work list of pending
  jobs suffices (at most logn + 1 <= 11 frames).

- falcon_ffSampling_fft has work interleaved between its sub-invocations,
  so it becomes a three-stage state machine over at most 8 frames. The
  reference's inlined base cases are split out into WC_INLINE helpers
  (ffSampling_fft_deg4 / ffSampling_fft_deg2), and a bounds check on logn
  keeps the frame stack safe even for invalid callers.

Sub-invocations run in exactly the reference order; for ffSampling this is
a hard requirement since the discrete Gaussian sampler consumes a PRNG
stream, and any reordering would change the produced signatures.

Verified with a differential harness (old recursive code from git vs the
new build): expanded keys and ffSampling outputs are bit-identical for
logn 1..10, with identical sampler invocation counts and an identical
order-sensitive hash of every (mu, isigma) sampler argument pair.
Performance is unchanged (within noise at logn 9 and 10).
Remaining fixes from the second review round:

- keygen: falcon_compute_public's scratch buffer holds NTT(f) (private-key
  material) in its tail; wc_ForceZero it before both frees (the
  f-not-invertible reject path and the success path). Also zeroize the
  internally allocated hwork for consistency with the tmpbuf hardening.

- sampler: falcon_sampler_z's rejection loop never consulted the sticky
  PRNG error flag, so a mid-signature SHAKE256 squeeze failure could make
  berexp deterministically reject and the loop spin forever. Check p.err
  each iteration and bail out; the returned value is discarded since
  falcon_sign_core rejects the whole signature once p.err is set.
  falcon_prng_init now frees the SHAKE256 context when a later init step
  fails (plugs a device-context leak in WOLFSSL_ASYNC_CRYPT builds), and
  falcon_prng_refill early-returns once the error is latched instead of
  re-issuing failing squeezes.

- codec: guard the bits-dependent shifts in falcon_trim_i8_encode/decode
  against out-of-range widths (defense in depth; callers only pass 5..8).

- check_key: implement the cryptographic private/public cross-check that
  91ebd89 documented as a follow-up. New falcon_native_check_key decodes
  (f, g) from the private key and h from the public key and verifies the
  defining relation h*f == g (mod q, mod X^n + 1) slot-wise in the NTT
  domain (falcon_ntt keeps values canonical in [0, q)); a slot with
  NTT(f) == 0 is rejected too, as keygen only emits invertible f.
  wc_falcon_check_key dispatches to it whenever the native signing core is
  compiled in, and falls back to the presence check in verify-only /
  callback-only builds. Doxygen updated to the actual contract, and a unit
  test added: a mismatched pair (public half from a different key) must
  fail with PUBLIC_KEY_E. This also strengthens the keypair validation
  done via wc_falcon_check_key in asn.c.
Regenerated from wolfssl-scripts falcon/x86_64/falcon.rb (commit
b680440), which documents in the file header that the backend is
SysV AMD64 ABI only and requires the default MXCSR state.  No code
change, comment only.
test_wc_falcon_sign_verify in tests/api/test_signature.c was merged to
master gated on HAVE_FALCON && HAVE_LIBOQS.  With liboqs removed the
macro is defined nowhere, so the test was dead code and check-source-text
failed with 'unrecognized macros used: HAVE_LIBOQS'.

Gate it on WC_FALCON_HAVE_NATIVE_SIGN like the rest of the native
signing tests, and replace the obsolete liboqs-RNG comment.  The tree
now has zero HAVE_LIBOQS references, so no .wolfssl_known_macro_extras
entry is needed.
- wc_falcon.c: replace the stale "Phase 1: verification only" file banner
  (the file now holds keygen/sign/verify cores).
- falcon.c: fix the garbled wc_falcon_verify_msg doc comment (removed a
  non-existent contextLen parameter; state the level-dependent BUFFER_E
  bound and the *res convention).
- wc_falcon.c: name the sign compression-fit retry bound
  FALCON_SIGN_MAX_ENCODE_RETRIES (was a bare 32) and document why the
  bound is safe, mirroring FALCON_SIGN_MAX_RESTARTS in wc_falcon_sign.c.
- test_falcon.c: add a direct wc_falcon_import_private_only concat(priv,pub)
  test that recovers the public key and signs+verifies from that single
  import, covering the recover-pub-from-concat path end to end.
Merge the nine wc_falcon_*.c cores into wolfcrypt/src/falcon.c, matching the
one-file-per-algorithm convention (ed25519.c, wc_mldsa.c). Sign and keygen are
gated internally on WOLFSSL_FALCON_VERIFY_ONLY and the internal helpers become
static. The fpr/fft/poly primitives stay external (shared with the separate
AVX2/NEON FFT backends) so their three headers are retained; the other five
internal headers are removed.
The max bit width for encoding F and G is a constant 8 for every Falcon
degree, so the per-logn lookup table carried no information. Replace it with
a FALCON_MAX_FG_BITS define. falcon_max_fg_bits stays a table (it genuinely
varies with logn).
Reflow the REV10 bit-reversal table into an aligned 12-per-row grid (values
right-justified) and collapse the one-value-per-line falcon_max_fg_bits into a
single row with a logn index legend. No functional change.
falcon_hash_to_point malloc'd a buffer solely to concatenate nonce and message
for a single wc_Shake256_Absorb. Since Absorb is an Update plus the SHAKE
finalize on the same sponge, feed the nonce with wc_Shake256_Update first and
the message with Absorb: same absorbed bytes, same state, but no allocation or
copy on every sign and verify.
Sign (8), keygen (5) and check_key (6) each carved their temporaries from that
many separate XMALLOCs; combine each into one alignment-ordered arena with a
single free and a single ForceZero over the secrets. Verify's ~8KB working set
(h/c/t/s2) now lives on the stack by default and only allocates under
WOLFSSL_SMALL_STACK. Cuts allocation churn to one call per operation.
Port the Falcon reference dynamic signer (sign_dyn / ffSampling_fft_dyntree),
which rebuilds the ffLDL tree inside the sampler instead of precomputing the
whole expanded key -- roughly halving the sign working set (Falcon-1024 ~168KB
to ~78KB) at the cost of redoing the tree work per attempt. The reference
recursion is flattened to an explicit stack per the no-recursion rule, and the
signer is byte-for-byte identical to the default path (verified with a
deterministic-RNG differential over both levels). Selected with
--enable-falcon=small-mem (WOLFSSL_FALCON_SIGN_SMALL_MEM); the eager tree signer
is compiled out in that mode.
falcon_prng_get_u64 and get_rng_u64 assembled a little-endian word64 from eight
explicit byte shifts. Replace both with a falcon_load_le64 helper: a single word
load on a little-endian CPU and a load plus byte-swap on a big-endian one, using
readUnalignedWord64/ByteReverseWord64. The spec-defined MSB-first bitstream codec
reads and the big-endian hash-to-point read are unchanged (their byte order is
fixed by the Falcon spec, not the CPU).
A review flagged zint_bezout as looking inefficient. It is in fact the reference
constant-time binary GCD: x and y derive from the secret key during keygen, so
its fixed-count loops and mask/sign-bit conditionals (rather than data-dependent
branches or early exits) are what keep the private key off the timing side
channel. It already reduces the operands ~31 bits per iteration word-wise. Add a
comment explaining this so the structure is not mistaken for a missed
optimization; no code change.
…rrett)

- configure.ac: list small-mem in the --enable-falcon help string and the
  sub-option doc block (the parser already accepted it).
- WOLFSSL_FALCON_SIGN_STATS: define falcon_sign_restart_count at file scope so
  the debug knob links, and drop the mid-function extern (C89 decl-at-top).
- falcon_native_check_key: reduce h*ft with falcon_barrett instead of a hardware
  '%', matching the verify path -- division-free and constant-time on ft (the NTT
  image of secret f).
The Falcon workflow only built --enable-falcon (default integer fpr). Add a
falcon_backends matrix that builds and runs testwolfcrypt for asm, double, avx2,
small-mem and avx2+small-mem on ubuntu-latest, and neon on the free ubuntu-24.04-arm
runner -- the SIMD/asm backends are the highest-risk code and now execute in CI.
verify-only is a build/link-only leg (its self-test needs signing). An AVX2 leg
guards on /proc/cpuinfo since backend dispatch is compile-time.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants